Please note that the groovy below is for example use, it will need to be adjusted for target requirements and also to cover failure scenarios
import java.util.Map;
import com.Navis.apex.business.model.GroovyInjectionBase;
import com.Navis.framework.persistence.HibernateApi;
import com.Navis.framework.portal.FieldChanges;
import com.Navis.framework.util.BizViolation;
import com.Navis.inventory.InventoryBizMetafield;
import com.Navis.inventory.InventoryField;
import com.Navis.inventory.business.units.EqBaseOrderItem;
import com.Navis.inventory.business.units.Unit;
import com.Navis.inventory.web.InventoryMobileUtil;
import com.Navis.orders.business.eqorders.EquipmentOrderItem;
/**
* This is an example Groovy class and is not readily production deployable. The idea is to provide a simple template that can be
* extended by the deployers.
* As the program below shows
* (1) Same (this) groovy class is called by each of the RDT programs (except Gate for which this plugin is not supported).
* (2) Each call comes with a sete of parameters including the Application Names (Rail, Yard, Hatch, Rail Inv, Reefer etc)
* (3) While most of the programs get an entity as part of the arguments, but entity can be null and should be handled as such.
* Specially in case of rail inventory
* there can be situations where entity is not created (or retrieved from the database) because no action is needed on a
* particular slot or because user entered possibly
* a wrong container number which would be caught later by the prebuilt product validations.
* This class is called when RDT's submit (commit) changes. The usecase is specfically applicable during inspection data
* posting that may need to go through additional
* validations that are not part of the product (out of the box)
*/
public class RdtCustomGroovyImpl extends GroovyInjectionBase {
final String CLASS_NAME = "RdtCustomGroovyImpl: ";
public void log(String inMsg){
super.log(CLASS_NAME+ inMsg );
}
public void validateChanges(Map args) throws BizViolation {
if (ignoreAllOverrideableErrors()) {
log("skipping groovy validations as override flag is set");
return;
} else {
if (args==null){
log("skipping groovy validations as required parameters are missing");
return;
}
String appName = (String) args.get(InventoryBizMetafield.RDT_APPLICATION_NAME);
if (appName==null){
log("skipping groovy validations as app is not set");
return;
}
Object entity = args.get(InventoryBizMetafield.RDT_ENTITY);
FieldChanges fcs = (FieldChanges) args.get(InventoryBizMetafield.RDT_FORM_CHANGES);
log(appName);
Unit unit;
// Hatch Clerk Program
if (appName.equals(InventoryMobileUtil.HATCH_CLERK_PROGRAM_NAME)) {
doExtraValidations(entity, fcs, appName);
// Rail or Yard Inspection Program get the same app name and we distinguish based on
// the container location.
} else if (appName.equals(InventoryMobileUtil.RAIL_OR_YARD_INSPECTION_PROGRAM_NAME)) {
unit = (Unit) entity;
if (unit.isUnitInYard()) {
log("Yard Inspection Program");
} else {
log(" Rail Inspection Program");
}
doExtraValidations(entity, fcs, appName);
// Rail Inventory program iterates over the rail car slots and makes
// a decision about load, position correction, removal, bump, swap etc.
// The example code below shows what is avalable in the parameters.
} else if (appName.equals(InventoryMobileUtil.RAIL_INVENTORY_PROGRAM_NAME)) {
unit = (Unit) entity;
String newPosName = (String) fcs.getFieldChange(InventoryField.POS_NAME).getNewValue();
String newUnitId = (String) fcs.getFieldChange(InventoryField.UNIT_ID).getNewValue();
String oldUnitId = (String) fcs.getFieldChange(InventoryField.UNIT_ID).getPriorValue();
log("=== Rail Inventory Program: New Unit Id: " + newUnitId);
log("=== Rail Inventory Program: New Position " + newPosName);
if (newUnitId == null || newUnitId.trim().length() == 0) {
log("Non-LOAD operation, returning without validations for: " + newPosName);
return;
}
if (newUnitId.equals(oldUnitId)) {
log("No change in the position, allowing the operation: " + newPosName);
return;
}
if (unit != null) {
log("TODO, Add additional Antwerp validation on unit " + newUnitId);
return;
} else {
log("TODO, N4 Could not locate a Unit..Returning " + newUnitId);
return;
}
} else if (appName.equals(InventoryMobileUtil.REEFER_MONITORING_PROGRAM_NAME)) {
doExtraValidations(entity, fcs, appName);
} else {
log("Uknown program type, skipping custom validations");
}
}
}
/**
* Internal Helper Method
* @param inEntity entity being updated or loaded. Can be NULL
* @param inFcs field changes
* @param inAppName Hand-held application ID.
*/
void doExtraValidations(Object inEntity, com.Navis.framework.portal.FieldChanges inFcs, String inAppName) {
Unit unit = (Unit) inEntity;
// by reading from the unit we get the new values applied in memory (not persisted yet)
Object seal = unit.getFieldValue(InventoryField.UNIT_SEAL_NBR1);
log("========== Unit Seal1:" + seal);
Boolean pm = (Boolean) unit.getFieldValue(InventoryField.UNIT_ARE_PLACARDS_MISMATCHED);
log(" ========== Unit Placard Mismatch?:" + pm);
if (pm != null && pm.booleanValue()) {
registerOverridableError("Required placards don't match with observed placards");
}
Object oogBack = unit.getFieldValue(InventoryField.UNIT_OOG_BACK_CM);
log("=========== Unit OOG Back:" + oogBack);
String eqType = "";
if (unit.getUnitPrimaryUe().getUeEquipment().getEqEquipType() != null) {
eqType = (String) unit.getUnitPrimaryUe().getUeEquipment().getEqEquipType().getEqtypId();
log("======== Unit ISO Code Being Updated: " + eqType);
}
// booking values
if (unit.getUnitPrimaryUe().getUeDepartureOrderItem() != null) {
EqBaseOrderItem bIso = unit.getUnitPrimaryUe().getUeDepartureOrderItem();
EquipmentOrderItem eqoi = (EquipmentOrderItem) HibernateApi.getInstance().downcast(bIso, EquipmentOrderItem.class);
log("======== Booking Reference : " + eqoi);
if (eqoi != null) {
String bIsoStr = eqoi.getEqoiSampleEquipType().getEqtypId();
log("Booking ISO Code: " + bIsoStr);
if (!bIsoStr.equals(eqType)) {
registerOverridableError("booking ISO Code: " + bIsoStr + " doesn't seem to match with unit value:" + eqType);
}
}
}
}
}
Example showing how to extract POW, CHE related fields from the parameters supplied to the Groovy Script:
import com.Navis.apex.business.model.GroovyInjectionBase
import com.Navis.framework.util.BizViolation
import com.Navis.inventory.InventoryBizMetafield
import com.Navis.inventory.business.atoms.RailInspectionOperationsEnum
import com.Navis.inventory.web.InventoryMobileUtil
import com.Navis.rail.variform.RailMobileVariforms
import com.Navis.rail.web.RailMobileGuiMetafield
class RdtCustomGroovyImpl extends GroovyInjectionBase {
// Main function called to perform the validations
void validateChanges(Map args) throws BizViolation {
this.log("validateChanges ignoreAllOverrideableErrors=${this.ignoreAllOverrideableErrors()}")
if (args == null) {
this.log('args cannot be null')
return
}
// We first need to find out what RDT program is calling us
def programName = args.get(InventoryBizMetafield.RDT_APPLICATION_NAME)
this.log("programName=$programName")
if (programName == null) {
this.log('exiting due to null programName')
return
}
// we retrieve the field changes. This is critical for determining what was modified by the RD user
def fieldChanges = args.get(InventoryBizMetafield.RDT_FORM_CHANGES)
//this.printFieldChanges(fieldChanges)
// see if variform data is present. Currently we populate that only for the Discharge operation.
def mobileFormData = fieldChanges.getFieldChange(InventoryBizMetafield.MOBILE_FORM_DATA)
this.log("Mobile form data=$mobileFormData")
if (mobileFormData == null) {
this.log('No Form data. Exiting')
return
}
if (InventoryMobileUtil.RAIL_OR_YARD_INSPECTION_PROGRAM_NAME.equals(programName)) {
def operation = fieldChanges.getFieldChange(InventoryBizMetafield.MOBILE_OPERATION_NAME).getNewValue()
this.log("Rail Operation =" + operation)
if (RailInspectionOperationsEnum.DISCHARGE.equals(operation)) {
//get Pow
def powFcs = mobileFormData.getNewValue().get(RailMobileVariforms.MRI017)
if (powFcs != null) {
def pow = powFcs.getFieldChange(RailMobileGuiMetafield.MR_CRANE_I_D).getNewValue()
this.log("Rail POW (Discharge) = $pow")
}
} else if (RailInspectionOperationsEnum.LOAD.equals(operation)) {
//get Pow
def powFcs = mobileFormData.getNewValue().get(RailMobileVariforms.MRI017)
if (powFcs != null) {
def pow = powFcs.getFieldChange(RailMobileGuiMetafield.MR_CRANE_I_D).getNewValue()
this.log("Rail POW (Load) = $pow")
}
}
}
}
}
Example showing how to stop dispatches to an inactive truck using N4 Mobile:
import com.Navis.apex.business.model.GroovyInjectionBase;
import com.Navis.argo.business.atoms.CheStatusEnum;
import com.Navis.argo.business.xps.model.Che;
import com.Navis.framework.portal.FieldChanges;
import com.Navis.framework.util.BizViolation;
import com.Navis.inventory.InventoryBizMetafield;
import com.Navis.inventory.business.api.InventoryCargoUtils;
import com.Navis.inventory.web.InventoryMobileUtil;
import com.Navis.vessel.business.operation.Vessel;
import com.Navis.vessel.variform.VesselMobileVariforms;
import com.Navis.vessel.web.VesselMobileGuiMetafield;
class RdtCustomGroovyImpl extends GroovyInjectionBase {
void validateChanges(Map args) throws BizViolation {
if (args == null) {
this.log("args cannot be null");
return;
}
Object programName = args.get(InventoryBizMetafield.RDT_APPLICATION_NAME);
if (programName == null) {
this.log("exiting due to null programName");
return;
}
FieldChanges fieldChanges = (FieldChanges) args.get(InventoryBizMetafield.RDT_FORM_CHANGES);
def mobileFormData = fieldChanges.getFieldChange(InventoryBizMetafield.MOBILE_FORM_DATA);
if (mobileFormData == null) {
this.log('No Form data. Exiting');
return;
}
def dischStatus = mobileFormData.getNewValue().get(VesselMobileVariforms.MNHCDschStatus);
String cheName = null;
if (dischStatus != null) {
cheName = dischStatus.getFieldChange(VesselMobileGuiMetafield.MNHC_LANE).getNewValue();
}
if (cheName != null) {
Che crane = InventoryCargoUtils.resolveCheByShortName(cheName);
if (crane != null && crane.getCheStatusEnum() != CheStatusEnum.WORKING) {
registerError("Truck is inactive state, cannot discharge");
}
}
}
}
Example showing how to stop dispatches to an inactive truck during discharge, load, or shift operation using the Manual Ops form in Crane Team UI (CTUI):
import com.Navis.apex.business.model.GroovyInjectionBase
import com.Navis.crane.presentation.view.manops.ManualOpsGroupFetch
import com.Navis.crane.variform.CraneVariforms
import com.Navis.framework.persistence.HibernateApi
import com.Navis.framework.portal.FieldChange
import com.Navis.framework.portal.FieldChanges
import com.Navis.framework.util.BizViolation
import com.Navis.inventory.InventoryBizMetafield
import com.Navis.inventory.InventoryField
import com.Navis.inventory.business.units.EqBaseOrderItem
import com.Navis.inventory.business.units.Unit
import com.Navis.inventory.portal.ApiConsts
import com.Navis.inventory.web.InventoryMobileUtil
import com.Navis.orders.business.eqorders.EquipmentOrderItem
public class RdtCustomGroovyImpl extends GroovyInjectionBase {
final String CLASS_NAME = "RdtCustomGroovyImpl: ";
public void log(String inMsg) {
super.log(CLASS_NAME + inMsg);
}
public void validateChanges(Map args) throws BizViolation {
if (ignoreAllOverrideableErrors()) {
log("skipping groovy validations as override flag is set");
return;
} else {
if (args == null) {
log("skipping groovy validations as required parameters are missing");
return;
}
String appName = (String) args.get(InventoryBizMetafield.RDT_APPLICATION_NAME);
if (appName == null) {
log("skipping groovy validations as app is not set");
return;
}
Object entity = args.get(InventoryBizMetafield.RDT_ENTITY);
FieldChanges fcs = (FieldChanges) args.get(InventoryBizMetafield.RDT_FORM_CHANGES);
log(appName);
Unit unit;
// Hatch Clerk Program
if (appName.equals(InventoryMobileUtil.HATCH_CLERK_PROGRAM_NAME)) {
FieldChange operNameFC = (FieldChange)fcs.getFieldChange(InventoryBizMetafield.MOBILE_OPERATION_NAME);
String opName = (String)operNameFC.getNewValue();
//Check if Groovy is called from CTUI or N4 Mobile
if (isCraneTeamUIHatchClerk(opName)){ //for CTUI
doCraneTeamValidations(entity, fcs, appName,opName );
}else { // for N4Mobile
}
// Rail or Yard Inspection Program get the same app name and we distinguish based on
// the container location.
} else {
// this is for unit test purpose
log("Uknown program type, skipping custom validations");
println("Uknown program type, skipping custom validations");
registerOverridableError("booking values don't seem to match with unit values");
}
}
}
/**
* Internal Helper Method
*
* @param inOpName Operation name
* @return boolean return whether the groovy called from CTUI or N4 Mobile
*/
private boolean isCraneTeamUIHatchClerk(String inOpName){
boolean isCraneTeamUI = false;
if ((CraneVariforms.FORM_MANUAL_OPS_DISCHARGE.equals(inOpName))
|| (CraneVariforms.FORM_MANUAL_OPS_LOAD.equals(inOpName))
|| (CraneVariforms.FORM_MANUAL_OPS_SHIFT.equals(inOpName))){
isCraneTeamUI = true;
}
return isCraneTeamUI;
}
/**
* Internal Helper Method
*
* @param inEntity entity being updated or loaded. Can be NULL
* @param inFcs field changes
* @param inAppName Hand-held application ID.
*/
private void doCraneTeamValidations(Object inEntity, com.Navis.framework.portal.FieldChanges inFcs, String inAppName, String inOpName ){
FieldChange manualOpsGroupFetchFC = (FieldChange) inFcs.getFieldChange(InventoryBizMetafield.MOBILE_FORM_DATA);
Map<String, ManualOpsGroupFetch> manualOpsGroupFetchMap = (Map<String, ManualOpsGroupFetch>) manualOpsGroupFetchFC.getNewValue();
ManualOpsGroupFetch manualOpsGroupFetch = manualOpsGroupFetchMap.get(ApiConsts.MANUAL_OPS_GROUP_FETCH);
String mode = manualOpsGroupFetch.getLiftMode().getOpMode().getKey();
log("CTUI mode " + mode);
if ((CraneVariforms.FORM_MANUAL_OPS_DISCHARGE.equals(inOpName))) {
log("Groovy called from Discharge Manual Operation screen in CTUI ");
}else if ((CraneVariforms.FORM_MANUAL_OPS_LOAD.equals(inOpName))){
log("Groovy called from Load Manual Operation screen in CTUI ");
}else if ((CraneVariforms.FORM_MANUAL_OPS_SHIFT.equals(inOpName))){
log("Groovy called from Shift Manual Operation screen in CTUI ");
}
}
}
Please note the following aspects:
The groovy class is extending the GroovyInjectionBase to inherit the prebuilt utility methods to log and to register overridable errors.
As shown GroovyInjectionBase class is enhanced and has a new method to record overridable errors (those errors that will be ignored when force button is pressed.
Yard and Rail inspection programs have the same posting logic for the inspection data. The way to differentiate the two programs in the groovy code is to use the Unit's transit state.
Groovy code is called after applying the field changes on the memory Unit object (ie they may not be necessarily persisted in the current transaction). This enables the groovy code to see computed fields like placardsMismatch.
Groovy example shows all the programs, in practice groovy can check for a smaller subset and simply return if it is called by other programs.